// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。wen dang


package http

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net/textproto"
	"net/url"
	"strconv"
	"strings"

	"golang.org/x/net/http/httpguts"
)

var respExcludeHeader = map[string]bool{
	"Content-Length":    true,
	"Transfer-Encoding": true,
	"Trailer":           true,
}

// Response表示来自HTTP请求的响应。
// 
// 一旦收到响应头，客户端和传输将从服务器返回响应。响应主体
// 在读取主体字段时按需流式传输。
type Response struct {
	Status     string // 例如“200 OK”
	StatusCode int    // 例如200 
	Proto      string // 例如“HTTP/1.0”
	ProtoMajor int    // 例如1 
	ProtoMinor int    // 例如0 

	// 头将头键映射到值。如果响应中有多个带有同一密钥的
	// 头，则可以使用逗号
	// 分隔符将它们连接起来。（RFC 7230第3.2.2节要求多个标题
	// 在语义上等同于逗号分隔的序列。）当此结构中的其他字段（例如，
	// 头值时，字段值为
	// ContentLength、Transferncode、Trailer）复制了
	// 权威。
	// 
	// 映射中的键已标准化（请参阅CanonicalHeaderKey）。
	Header Header

	// Body代表响应体。
	// 
	// 在读取正文字段
	// 时，响应正文将按需流式传输。如果网络连接失败或服务器
	// 终止响应，则返回Body。读取调用返回错误。
	// 
	// http客户端和传输保证Body始终为
	// 非零，即使在没有Body的响应或带有
	// 零长度Body的响应上也是如此。呼叫方有责任关闭身体。默认HTTP客户端的传输不能
	// 重用HTTP/1。如果正文为
	// 未读到完成并关闭，则为x“保持活动”TCP连接。
	// 
	// 如果服务器使用“分块”传输编码回复
	// 则正文将自动取消分块。
	// 
	// 从Go 1.12开始，该机构也将实现io。作者
	// 关于一个成功的“101交换协议”响应，
	// 由WebSocket和HTTP/2的“h2c”模式使用。
	Body io.ReadCloser

	// ContentLength记录关联内容的长度。
	// 值-1表示长度未知。除非有人要求。方法
	// 是“HEAD”，值>=0表示可以从Body读取给定数量的字节。
	ContentLength int64

	// 包含从最外层到最内层的传输编码。值为
	// nil，表示使用“标识”编码。
	TransferEncoding []string

	// Close记录头是否指示连接在读取Body后关闭。其价值是给客户的建议：既不是
	// ReadResponse，也不是Response。Write会关闭连接。
	Close bool

	// Uncompressed报告响应是否被压缩发送，但
	// 是否被http包解压缩。如果为true，则从
	// 正文中读取将生成未压缩的内容，而不是从服务器中实际设置的压缩的
	// 内容，ContentLength设置为-1，
	// 并且从responseHeader中删除“content Length”和“content Encoding”字段。要从
	// 服务器获取原始响应，请设置Transport。不允许压缩为真。
	Uncompressed bool

	// 拖车将拖车键映射到与标题格式相同的值。
	// 
	// 尾部最初只包含零值，一个用于
	// 服务器的“尾部”头中指定的每个键
	// 值。这些值不会添加到标题中。
	// 
	// 不能与正文上的读取调用
	// 同时访问尾部。
	// 
	// Body后面。里德已经回来了。EOF，拖车将包含
	// 服务器发送的任何拖车值。
	Trailer Header

	// 请求是为获取此响应而发送的请求。
	// 请求的正文为零（已被消费）。
	// 这只为客户端请求填充。
	Request *Request

	// TLS包含有关接收到
	// 响应的TLS连接的信息。未加密的响应为零。
	// 指针在响应之间共享，不应被
	// 修改。
	TLS *tls.ConnectionState
}

// Cookies解析并返回set Cookie头中设置的Cookies。
func (r *Response) Cookies() []*Cookie {
	return readSetCookies(r.Header)
}

// 当不存在位置头时，响应的位置方法
// 将返回ErrNoLocation。
var ErrNoLocation = errors.New("http: no Location header in response")

// Location返回响应的“Location”头的URL，如果存在，返回
// 。相对重定向是相对于
// 响应的请求来解决的。如果不存在
// 位置标头，则返回ErrNoLocation。
func (r *Response) Location() (*url.URL, error) {
	lv := r.Header.Get("Location")
	if lv == "" {
		return nil, ErrNoLocation
	}
	if r.Request != nil && r.Request.URL != nil {
		return r.Request.URL.Parse(lv)
	}
	return url.Parse(lv)
}

// ReadResponse读取并返回来自r的HTTP响应。
// req参数可选地指定与此响应对应的请求。如果为nil，则假定为GET请求。
// 客户必须呼叫resp。身体阅读完毕后关闭。身体
// 在那次通话之后，客户可以检查resp。Trailer查找响应Trailer中包含的键/值
// 对。
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) {
	tp := textproto.NewReader(r)
	resp := &Response{
		Request: req,
	}

	// 解析响应的第一行。
	line, err := tp.ReadLine()
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return nil, err
	}
	proto, status, ok := strings.Cut(line, " ")
	if !ok {
		return nil, badStringError("malformed HTTP response", line)
	}
	resp.Proto = proto
	resp.Status = strings.TrimLeft(status, " ")

	statusCode, _, _ := strings.Cut(resp.Status, " ")
	if len(statusCode) != 3 {
		return nil, badStringError("malformed HTTP status code", statusCode)
	}
	resp.StatusCode, err = strconv.Atoi(statusCode)
	if err != nil || resp.StatusCode < 0 {
		return nil, badStringError("malformed HTTP status code", statusCode)
	}
	if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok {
		return nil, badStringError("malformed HTTP version", resp.Proto)
	}

	// 解析响应头。
	mimeHeader, err := tp.ReadMIMEHeader()
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return nil, err
	}
	resp.Header = Header(mimeHeader)

	fixPragmaCacheControl(resp.Header)

	err = readTransfer(resp, r)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

// RFC 7234，第5.4节：应处理
// Pragma:无缓存
// 类似于
// 缓存控制：无缓存
func fixPragmaCacheControl(header Header) {
	if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" {
		if _, presentcc := header["Cache-Control"]; !presentcc {
			header["Cache-Control"] = []string{"no-cache"}
		}
	}
}

// 协议至少报告在响应中使用的HTTP协议是否至少是主要的。少数的
func (r *Response) ProtoAtLeast(major, minor int) bool {
	return r.ProtoMajor > major ||
		r.ProtoMajor == major && r.ProtoMinor >= minor
}

// 在HTTP/1中写入r到w。x服务器响应格式，
// 包括状态行、标题、正文和可选尾部。
// 
// 此方法参考响应r的以下字段：
// 
// 状态码
// ProtoMajor 
// ProtoMinor 
// 请求。方法
// TransferEncoding 
// Trailer 
// Body 
// ContentLength 
// Header，非规范键的值将具有不可预测的行为
// 
// 响应体在发送后关闭。
func (r *Response) Write(w io.Writer) error {
	// 状态行
	text := r.Status
	if text == "" {
		var ok bool
		text, ok = statusText[r.StatusCode]
		if !ok {
			text = "status code " + strconv.Itoa(r.StatusCode)
		}
	} else {
		// 如果用户将r.Status设置为“200 OK”，StatusCode设置为200，则只是为了减少口吃。
		// 不重要。
		text = strings.TrimPrefix(text, strconv.Itoa(r.StatusCode)+" ")
	}

	if _, err := fmt.Fprintf(w, "HTTP/%d.%d %03d %s\r\n", r.ProtoMajor, r.ProtoMinor, r.StatusCode, text); err != nil {
		return err
	}

	// 克隆它，我们可以根据需要修改r1。
	r1 := new(Response)
	*r1 = *r
	if r1.ContentLength == 0 && r1.Body != nil {
		// 它实际上是0长度吗？还是不知道？
		var buf [1]byte
		n, err := r1.Body.Read(buf[:])
		if err != nil && err != io.EOF {
			return err
		}
		if n == 0 {
			// 将其重置为已知的零读卡器，以防一个
			// 的底层被重复读取而不高兴。
			r1.Body = NoBody
		} else {
			r1.ContentLength = -1
			r1.Body = struct {
				io.Reader
				io.Closer
			}{
				io.MultiReader(bytes.NewReader(buf[:1]), r.Body),
				r.Body,
			}
		}
	}
	// 如果我们发送的是非分块的HTTP/1.1响应，而没有
	// 内容长度，那么唯一的方法就是使用旧的HTTP/1.0 
	// 方法，通过注意连接关闭的EOF，所以我们需要
	// 来设置关闭。
	if r1.ContentLength == -1 && !r1.Close && r1.ProtoAtLeast(1, 1) && !chunked(r1.TransferEncoding) && !r1.Uncompressed {
		r1.Close = true
	}

	// 进程正文、ContentLength、Close、Trail 
	tw, err := newTransferWriter(r1)
	if err != nil {
		return err
	}
	err = tw.writeHeader(w, nil)
	if err != nil {
		return err
	}

	// 头的其余部分
	err = r.Header.WriteSubset(w, respExcludeHeader)
	if err != nil {
		return err
	}

	// ContentLengthReadySent可能已经为
	// POST/PUT请求发送，即使长度为零。见第8180期。
	contentLengthAlreadySent := tw.shouldSendContentLength()
	if r1.ContentLength == 0 && !chunked(r1.TransferEncoding) && !contentLengthAlreadySent && bodyAllowedForStatus(r.StatusCode) {
		if _, err := io.WriteString(w, "Content-Length: 0\r\n"); err != nil {
			return err
		}
	}

	// 头的结尾
	if _, err := io.WriteString(w, "\r\n"); err != nil {
		return err
	}

	// 写入正文和尾部
	err = tw.writeBody(w)
	if err != nil {
		return err
	}

	// 成功
	return nil
}

func (r *Response) closeBody() {
	if r.Body != nil {
		r.Body.Close()
	}
}

// 正文可写报告正文是否支持写入。
// 传输返回101个交换协议的可写体
// 响应。
// 传输使用此方法确定是否从其角度管理了一个持久的
// 连接。一旦我们
// 向用户返回一个可写的响应体，net/http包就是
// 管理完该连接。
func (r *Response) bodyIsWritable() bool {
	_, ok := r.Body.(io.Writer)
	return ok
}

// isProtocolSwitch报告响应代码和头
// 是否指示成功的协议升级响应。
func (r *Response) isProtocolSwitch() bool {
	return isProtocolSwitchResponse(r.StatusCode, r.Header)
}

// isProtocolSwitchResponse报告响应代码和
// 响应头是否指示成功的协议升级响应。
func isProtocolSwitchResponse(code int, h Header) bool {
	return code == StatusSwitchingProtocols && isProtocolSwitchHeader(h)
}

// isProtocolSwitchHeader报告请求或响应头
// 是否用于协议交换机。
func isProtocolSwitchHeader(h Header) bool {
	return h.Get("Upgrade") != "" &&
		httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade")
}
